package org.jjche.serialize.dict.plugin;

import lombok.AllArgsConstructor;
import org.jjche.common.annotation.Dict;
import org.jjche.common.util.ClassUtil;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.lang.Nullable;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * <p>
 * springMVC 枚举类的入参转换器
 * </p>
 * <p>
 * 如果枚举类中有工厂方法(静态方法)被标记为{EnumConvertMethod},则调用该方法转为枚举对象.
 * </p>
 *
 * @author miaoyj
 * @version 1.0.0-SNAPSHOT
 * @since 2020-07-09
 */
@SuppressWarnings("all")
public class EnumMvcConverterFactory implements ConverterFactory<String, Dict<?>> {

    private final ConcurrentMap<Class<? extends Dict<?>>, EnumMvcConverterHolder> holderMapper = new ConcurrentHashMap<>();


    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends Dict<?>> Converter<String, T> getConverter(Class<T> targetType) {
        EnumMvcConverterHolder holder;
        holder = holderMapper.computeIfAbsent(targetType,
                EnumMvcConverterHolder::createHolder);
        return (Converter<String, T>) holder.converter;
    }


    @AllArgsConstructor
    static class EnumMvcConverterHolder {
        @Nullable
        final EnumMvcConverter<?> converter;

        static EnumMvcConverterHolder createHolder(Class<? extends Dict<?>> targetType) {
            if (ClassUtil.isAssignable(Dict.class, targetType)) {
                return new EnumMvcConverterHolder(new EnumMvcConverter<>(targetType));
            }
            return new EnumMvcConverterHolder(null);
        }
    }

    static class EnumMvcConverter<T extends Dict<T>> implements Converter<String, T> {

        private final Class<? extends Dict> targetType;

        public EnumMvcConverter(Class<? extends Dict> targetType) {
            this.targetType = targetType;
        }

        @Override
        public T convert(String source) {
            if (source.isEmpty()) {
                // reset the enum value to null.
                return null;
            }
            return (T) Dict.getByCode(this.targetType, source);
        }
    }
}
